package bigoo.common.communication.tcp;

import static java.util.logging.Logger.GLOBAL_LOGGER_NAME;

import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.logging.Logger;

import bigoo.common.PeerInfo;
import bigoo.common.communication.AbstractCommunication;
import bigoo.common.communication.InvalidMessageException;
import bigoo.common.communication.Message;
import bigoo.common.communication.UnknownMessageException;

public class TCPCommunication extends AbstractCommunication {

    private Logger logger = Logger.getLogger(GLOBAL_LOGGER_NAME);
    private SocketWrapperManager pool = new SocketWrapperManager();
    private ServerSocket sSocket;
    private TCPDaemon daemon;

    public TCPCommunication(int port) throws IOException {
	this.sSocket = new ServerSocket(port);
	this.daemon = new TCPDaemon();
    }

    @Override
    public void sendMessage(Message message, PeerInfo destPeer) throws IOException {
	String peerID = destPeer.getPeerID();
	try {

	    if (!this.pool.containsSocket(peerID)) {
		Socket sock = new Socket();
		SocketAddress address = new InetSocketAddress(destPeer.getAddress().trim(), destPeer.getPort());
		sock.connect(address);
		SocketWrapper wrapper = new SocketWrapper(sock, this.daemon, this.daemon); // TODO
		this.pool.registerSocketWrapper(peerID, wrapper);
	    }
	    String msg = message.getRawMessage();
	    logger.fine("[sending msg: " + peerID + "] >> " + msg);
	    this.pool.getSocketWrapper(peerID).sendMessage(msg);
	} catch (ConnectException e) {
	    logger.warning("Failed to connect to peer " + peerID);
	    fireFailure(peerID);
	}
    }

    @Override
    public void closeConnection(PeerInfo peer) {
	try {
	    logger.fine("Closing connection with " + peer.getPeerID());
	    this.pool.closeSocketWrapper(peer.getPeerID());
	} catch (IOException e) {
	    logger.warning("Error closing connection with peer " + peer.getPeerID() + ". " + e.getLocalizedMessage());
	}
    }

    @Override
    public void start() {
	if (!this.daemon.isRunning)
	    new Thread(this.daemon).start();
    }

    private class TCPDaemon implements Runnable, RawMessageReceiver, SocketWrapperFailuredInterested {

	boolean isRunning = false;

	@Override
	public void run() {
	    isRunning = true;
	    while (true) {
		try {
		    Socket sock = sSocket.accept();
		    SocketWrapper rsw = new SocketWrapper(sock, this, this);
		    rsw.start(); // TODO bugs podem fazer com que muitas threads
		    // comecem a rodar...
		} catch (IOException e) {
		    logger.warning(e.getLocalizedMessage());
		}
	    }
	}

	@Override
	public void processRawMessage(String rawMessage, SocketWrapper wrapper) {
	    try {
		logger.fine("[received msg] >> " + rawMessage);
		Message msg = getMessage(rawMessage);
		pool.registerSocketWrapper(msg.getSenderID(), wrapper);
		deliverMessage(msg);
	    } catch (UnknownMessageException e) {
		logger.warning("Unknown Message Received: " + rawMessage);
	    } catch (InvalidMessageException e) {
		logger.warning("Invalid Message Received: " + e.getLocalizedMessage());
	    }
	}

	@Override
	public void socketClosed(SocketWrapper socketWrapper) {
	    String peerID = pool.findPeerID(socketWrapper);
	    try {
		if (peerID != null) {
		    logger.fine("Connection with peer " + peerID + " closed by remote side.");
		    pool.closeSocketWrapper(peerID);
		    fireFailure(peerID);
		}
	    } catch (IOException e) {
		logger.warning("Error closing connection with peer " + peerID + ". " + e.getLocalizedMessage());
	    }
	}
    } // end inner-class
}
